##############
# (*) Kaleidoscope
# By: Allan Spale
# June 2008

from Kaleidoscope.KSColumn import KSColumn

import sys

class KSQueryItemError(Exception):
    """
    Exception class for KSQueryItem

    """
    # REDO ALL CONSTANT PROPERTIES TO BE NORMAL/WRITABLE OUTSIDE OF CLASS...
    # FIX THIS LATER

    B_UNKNOWN_EXCEPTION = True
    B_CLASS_EXCEPTION = False


    def __init__(self, u_message):
        self.__u_message = u_message
    def __str__(self):
        return repr(self.__u_message)

    @staticmethod
    def print_and_raise_error(u_message, b_unknown_exception):
        """
        Convenience method for printing and raising an error for this
        particular Exception subclass.

        """
        #sys.stderr.write(sys.exc_info())  # write initial error
        u_message = u'KSQueryItem.py: ' + u_message
        sys.stderr.write(u_message + u'\n')

        if (b_unknown_exception):
            raise Exception(u_message)
        else:
            raise KSQueryItemError(u_message)


class KSQueryItem(object):
    """
    This class holds an individual query operation. The legal query operations
    will retrieve keys based on a given value or range of values using
    KSColumn methods.

    """
    def __init__(self, ksc_column, u_fn_query_name, list_parameters):
        """
        Create a query item using a KSColumn and providing a function
        query name and its parameters as would normally be issued using
        KSColumn. This probably is not the best way to do it, but it seemed
        like a good idea when it was first written.

        """
        self.__dict_fn_allowed = {}
        self.__list_parameters = []

        self.__dict_fn_allowed[u'get_data_objects_from_key_range'] = \
            self.__wrapped_get_data_objects_from_key_range
        self.__dict_fn_allowed[u'get_keys_from_data_object_range'] = \
            self.__wrapped_get_keys_from_data_object_range
        self.__dict_fn_allowed[u'get_keys_from_data_value'] = \
            self.__wrapped_get_keys_from_data_value
        self.__dict_fn_allowed[u'get_data_from_key'] = \
            self.__wrapped_get_data_from_key
        self.__dict_fn_allowed[u'has_value'] = \
            self.__wrapped_has_value


        self.__list_result_keys = []

        if (isinstance(u_fn_query_name, unicode) and
            u_fn_query_name in self.__dict_fn_allowed.keys()):
                self.__u_fn_query_name = u_fn_query_name
        else:
            KSQueryItemError.print_and_raise_error(u'The function given for ' \
                + u'the query is not allowed.',
                KSQueryItemError.B_CLASS_EXCEPTION)

        if (isinstance(ksc_column, KSColumn)):
            self.__ksc_column = ksc_column
            self.get_transaction = self.__ksc_column.get_transaction
        else:
            KSQueryItemError.print_and_raise_error(u'Queries may only be ' \
                + u'performed on KSColumn columns.',
                KSQueryItemError.B_CLASS_EXCEPTION)

        # Again, because of variable parameters, have to just assign this
        # and hope for the best later (more complicated checks could be
        # implemented, but this is not a priority right now).
        self.change_parameter_list(list_parameters)


    def change_parameter_list(self, list_parameters):
        """
        In order to minimize the creation and desruction of objects, allow
        a KSQueryItem object to be created and permit the changing of its
        parameters. This will allow a different result to occur when
        execute_query is executed.

        """
        if (isinstance(list_parameters, list)):
            self.__list_parameters = list_parameters
        else:
            KSQueryItemError.print_and_raise_error(u'The parameters for the '\
                + u'query must exist in a list.',
                KSQueryItemError.B_CLASS_EXCEPTION)


    def execute_query(self, kstx_parent_transaction):
        """
        Function that will call the query function for the given KSColumn.
        Results are not directly returned from this function because caching
        of results seemed like a good idea when this class was written, but
        is not being done currently.

        """
        try:
            # There is a better way to handle transactions, but it will
            # have to wait until later.

            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.execute_query)
            result = self.__dict_fn_allowed[self.__u_fn_query_name](
                kstx_parent_transaction, self.__list_parameters)

            if (isinstance(result, list)):
                self.__list_result_keys = result
            else:
                self.__list_result_keys = [result]

            kstx_transaction.commit(0, self.execute_query)

        except Exception:
            kstx_transaction.cancel()
            KSQueryItemError.print_and_raise_error(u'The query could not be '
                + u'executed because of an error.',
                KSQueryItemError.B_CLASS_EXCEPTION)


    def invert_result_set(self, kstx_parent_transaction):
        """
        Take the existing result set, which is actually a set type, and perform
        an inverse set operation on the result values. To simplify this
        operation, use the existing set operator provided by Python.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.invert_result_set)
            result = set(self.__ksc_column.keys(kstx_transaction,
                KSColumn.B_REGULAR_DATA)) ^ set(self.__list_result_keys)
            kstx_transaction.commit(0, self.invert_result_set)
            return result

        except Exception:
            kstx_transaction.cancel()
            KSQueryItemError.print_and_raise_error(u'The query result set '
                + u'could not be inverted because of an error.',
                KSQueryItemError.B_CLASS_EXCEPTION)


    def get_result_set(self):
        """
        Return the result set as a set type. Normally this is not particularly
        useful unless other set operations will be performed using this set.

        """
        return set(self.__list_result_keys)

    def clear_results(self):
        """
        Clear out the result set.

        """
        self.__list_result_keys = []


    @staticmethod
    def convert_set_to_list(set_items):
        """
        Convenience function that will convert a set function to something a
        bit more useful with lists and editable items.

        """
        list_items = []
        for item in set_items:
            list_items.append(item)

        return list_items


    # PRIVATE
    def __wrapped_get_data_objects_from_key_range(self,
                                                  kstx_transaction, *seqmain):
        """
        Wrapped function for the KSColumn function
        get_data_objects_from_key_range.

        """
        seq = seqmain[0]
        return self.__ksc_column.get_data_objects_from_key_range(seq[0],
            seq[1], seq[2], seq[3], kstx_transaction, seq[5])


    def __wrapped_get_keys_from_data_object_range(self,
                                                  kstx_transaction, *seqmain):
        """
        Wrapped function for the KSColumn function
        get_keys_from_data_object_range.

        """
        seq = seqmain[0]
        return self.__ksc_column.get_keys_from_data_object_range(seq[0],
            seq[1], seq[2], seq[3], kstx_transaction, seq[5])


    def __wrapped_get_keys_from_data_value(self,
                                                  kstx_transaction, *seqmain):
        """
        Wrapped function for the KSColumn function get_keys_from_data_value.

        """
        seq = seqmain[0]
        return self.__ksc_column.get_keys_from_data_value(
            seq[0], kstx_transaction, seq[2])


    def __wrapped_get_data_from_key(self, kstx_transaction, *seqmain):
        """
        Wrapped function for the KSColumn function get_data_from_key.

        """
        seq = seqmain[0]
        return self.__ksc_column.get_data_from_key(
            seq[0], kstx_transaction, seq[2])


    def __wrapped_has_value(self, kstx_transaction, *seqmain):
        """
        Wrapped function for the KSColumn function has_value.

        """
        seq = seqmain[0]
        return self.__ksc_column.has_value(seq[0], kstx_transaction)

    '''
    ### LOGICAL OPERATORS TO ALLOW FOR CHAINING OF COMMANDS
    # AND = &, OR = |, NOT = unary -
    def __and__(self, ksqi_rhs):
        stuff = u'result of ' + str(self) + u' AND ' + str(ksqi_rhs)
        print 'evaluating: ', stuff
        return KSQuery(stuff)

    def __or__(self, ksqi_rhs):
        stuff = u'result of ' + str(self) + u' OR ' + str(ksqi_rhs)
        print 'evaluating: ', stuff
        return KSQuery(stuff)

    def __neg__(self):
        stuff = u'result of NOT ' + str(self)
        print 'evaluating: ', stuff
        return KSQuery(stuff)
    '''
